from typing import Union
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from fastapi.staticfiles import StaticFiles
from fastapi.openapi.utils import get_openapi
from sqlalchemy import select
from sqladmin import Admin, ModelView
from sqladmin.authentication import AuthenticationBackend
from routers import blogs, users
from db.session import init_models, engine, async_session
from admin import userAdmin, blogAdmin
from schema.response.baseResponse import CustomException as Cu
from model.user import User
from config.setting import SECRET_KEY
from utils.encryAbout import verify_password
from config.setting import SERVER_LOG
import json



app = FastAPI()


# 配置 swagger UI ， 或者 直接修改header 参数为我token 在每个接口进行传递 不需要配置 swagger UI 
def custom_openapi():
    if app.openapi_schema:
        return app.openapi_schema
    openapi_schema = get_openapi(
        title="My API",
        version="1.0.0",
        description="This is a custom OpenAPI schema",
        routes=app.routes,
    )
    # 添加 securitySchemes 配置
    openapi_schema["components"]["securitySchemes"] = {
        "BearerAuth": {
            "type": "http",
            "scheme": "bearer",
            "bearerFormat": "JWT",
        }
    }

    openapi_schema["security"] = [{"BearerAuth": [], "RefreshToken": []}]
    app.openapi_schema = openapi_schema
    return app.openapi_schema

app.openapi = custom_openapi

# 定义全局异常处理器，在有问题的时候跑出了异常，捕捉(捕捉 Cu 类型的错误)，然后返回对应的code码和msg信息
@app.exception_handler(Cu)
async def exception_handler(request, exc: Cu):
    return JSONResponse(
        status_code=200,
        content={"code": exc.detail["code"], "msg": exc.detail["msg"]}
    )

# 定义全局中间件( 看情况也可以避免这个直接在 具体接口中通过 token:str = Depends(decode_token_by_head) )
# (增加请求和返回日志)
@app.middleware("http")
async def add_middleware(request, call_next):
    # req = await request.body()
    # 正常应该生成一个唯一码标识 request 和 reponse 是一对 但是目前reponse 记录还有些问题先不加了
    # SERVER_LOG.info(f"req {request.method} {request.url} {request.headers} {json.loads(req)}")
    response = await call_next(request)
    # body_iterator = response.body_iterator
    # res = b""
    # async for chunk in body_iterator:
    #     res += chunk

    # SERVER_LOG.info(f"{request.method} {request.url} {request.headers} {response.headers}")
    # response 是一个 stream ， body_iterator是一个迭代器, 读出来还需要重新创建一个返回（考虑有没有更好的办法，还没有找到）
    return response


class AdminAuth(AuthenticationBackend):
    """sqladmin 登陆认证
        对于sqladmin 不想自定义登陆界面了, 根据加密方式重新实现了默认的登陆；
        自定义页面+使用实现的login接口 需要结合middleware进行拦截,并将token维护在会话中，在middleware拦截的时候通过,从会话取出token进行解析验证 
    Args:
        AuthenticationBackend (_type_): _description_
    """
    async def login(self, request: Request) -> bool:
        form = await request.form()
        username, password = form["username"], form["password"]
        # 验证用户名和密码（sqladmin 不支持fastapi的 上下文管理器）
        async with async_session() as db:
            # 只有超级管理员可以登陆
            stmt = select(User).where(User.username == username, User.is_supper_user == 1)
            users = await db.execute(stmt)
            user = users.scalars().first()

        if user and verify_password(password, user.password):
            # 登录成功，设置会话
            request.session.update({"username": username})
            return True
        return False

    async def logout(self, request: Request) -> bool:
        # 登出，清除会话
        request.session.clear()
        return True

    async def authenticate(self, request: Request) -> bool:
        # 检查用户是否已登录
        username = request.session.get("username")
        if username:
            return True
        return False

app.mount("/static", StaticFiles(directory="static"), name="static")
# 尝试引入一个富文本框(这个很奇怪，最开始配置找不到路径，看了一下源码，调试了一下源码加载方式， 后来又可以了，可能是缓存原因)
# sqladmin 默认会从个自己的 template 目录加载 页面，配置了 templates_dir="templates" 会增加一个页面目录，如果要使用自己的，要在admin指明
admin = Admin(app, engine, authentication_backend=AdminAuth(secret_key=SECRET_KEY), templates_dir="templates")
print(dir(admin.templates.env.loader))
print(admin.templates.env.loader.list_templates())
app.include_router(blogs.router)
app.include_router(users.router)


admin.add_view(userAdmin.UserAdmin)
admin.add_view(userAdmin.GoldCoinsInfoAdmin)
admin.add_view(userAdmin.GoldCoinsLogAdmin)
admin.add_view(blogAdmin.BlogAdmin)
admin.add_view(blogAdmin.BlogCategoryAdmin)

# @app.on_event("startup")
# async def on_startup():
#     await init_models()